# SPDX-License-Identifier: MIT

"""
Typing examples that rely on Mypy-specific features (mostly the attrs plugin).
"""

from __future__ import annotations

import re

from typing import Any, Dict, List, Tuple

import attr
import attrs


# Typing via "type" Argument ---


@attr.s
class C:
    a = attr.ib(type=int)


c = C(1)
C(a=1)


@attr.s
class D:
    x = attr.ib(type=List[int])


@attr.s
class E:
    y = attr.ib(type="List[int]")


@attr.s
class F:
    z = attr.ib(type=Any)


# Typing via Annotations ---


@attr.s
class CC:
    a: int = attr.ib()


cc = CC(1)
CC(a=1)


@attr.s
class DD:
    x: list[int] = attr.ib()


@attr.s
class EE:
    y: "list[int]" = attr.ib()


@attr.s
class FF:
    z: Any = attr.ib()


@attrs.define
class FFF:
    z: int


FFF(1)


# Inheritance --


@attr.s
class GG(DD):
    y: str = attr.ib()


GG(x=[1], y="foo")


@attr.s
class HH(DD, EE):
    z: float = attr.ib()


HH(x=[1], y=[], z=1.1)


# same class
c == cc


# Exceptions
@attr.s(auto_exc=True)
class Error(Exception):
    x: int = attr.ib()


try:
    raise Error(1)
except Error as e:
    e.x
    e.args
    str(e)


@attrs.define
class Error2(Exception):
    x: int


try:
    raise Error2(1)
except Error as e:
    e.x
    e.args
    str(e)

# Field aliases


@attrs.define
class AliasExample:
    without_alias: int
    _with_alias: int = attr.ib(alias="_with_alias")


attr.fields(AliasExample).without_alias.alias
attr.fields(AliasExample)._with_alias.alias


# Converters


@attr.s
class ConvCOptional:
    x: int | None = attr.ib(converter=attr.converters.optional(int))


ConvCOptional(1)
ConvCOptional(None)


# XXX: Fails with E: Unsupported converter, only named functions, types and lambdas are currently supported  [misc]
# See https://github.com/python/mypy/issues/15736
#
# @attr.s
# class ConvCPipe:
#     x: str = attr.ib(converter=attr.converters.pipe(int, str))
#
#
# ConvCPipe(3.4)
# ConvCPipe("09")
#
#
# @attr.s
# class ConvCDefaultIfNone:
#     x: int = attr.ib(converter=attr.converters.default_if_none(42))
#
#
# ConvCDefaultIfNone(1)
# ConvCDefaultIfNone(None)


@attr.s
class ConvCToBool:
    x: int = attr.ib(converter=attr.converters.to_bool)


ConvCToBool(1)
ConvCToBool(True)
ConvCToBool("on")
ConvCToBool("yes")
ConvCToBool(0)
ConvCToBool(False)
ConvCToBool("n")


# Validators
@attr.s
class Validated:
    a = attr.ib(
        type=List[C],
        validator=attr.validators.deep_iterable(
            attr.validators.instance_of(C), attr.validators.instance_of(list)
        ),
    )
    a2 = attr.ib(
        type=Tuple[C],
        validator=attr.validators.deep_iterable(
            attr.validators.instance_of(C), attr.validators.instance_of(tuple)
        ),
    )
    a3 = attr.ib(
        type=Tuple[C],
        validator=attr.validators.deep_iterable(
            [attr.validators.instance_of(C)],
            [attr.validators.instance_of(tuple)],
        ),
    )
    b = attr.ib(
        type=List[C],
        validator=attr.validators.deep_iterable(
            attr.validators.instance_of(C)
        ),
    )
    c = attr.ib(
        type=Dict[C, D],
        validator=attr.validators.deep_mapping(
            attr.validators.instance_of(C),
            attr.validators.instance_of(D),
            attr.validators.instance_of(dict),
        ),
    )
    d = attr.ib(
        type=Dict[C, D],
        validator=attr.validators.deep_mapping(
            attr.validators.instance_of(C), attr.validators.instance_of(D)
        ),
    )
    d2 = attr.ib(
        type=Dict[C, D],
        validator=attr.validators.deep_mapping(attr.validators.instance_of(C)),
    )
    d3 = attr.ib(
        type=Dict[C, D],
        validator=attr.validators.deep_mapping(
            value_validator=attr.validators.instance_of(C)
        ),
    )
    d4 = attr.ib(
        type=Dict[C, D],
        validator=attr.validators.deep_mapping(
            key_validator=[attr.validators.instance_of(C)],
            value_validator=[attr.validators.instance_of(C)],
            mapping_validator=[attr.validators.instance_of(dict)],
        ),
    )
    e: str = attr.ib(validator=attr.validators.matches_re(re.compile(r"foo")))
    f: str = attr.ib(
        validator=attr.validators.matches_re(r"foo", flags=42, func=re.search)
    )

    # Test different forms of instance_of
    g: int = attr.ib(validator=attr.validators.instance_of(int))
    h: int = attr.ib(validator=attr.validators.instance_of((int,)))
    j: int | str = attr.ib(validator=attr.validators.instance_of((int, str)))
    k: int | str | C = attr.ib(
        validator=attrs.validators.instance_of((int, C, str))
    )
    kk: int | str | C = attr.ib(
        validator=attrs.validators.instance_of(int | C | str)
    )

    l: Any = attr.ib(
        validator=attr.validators.not_(attr.validators.in_("abc"))
    )
    m: Any = attr.ib(
        validator=attr.validators.not_(
            attr.validators.in_("abc"), exc_types=ValueError
        )
    )
    n: Any = attr.ib(
        validator=attr.validators.not_(
            attr.validators.in_("abc"), exc_types=(ValueError,)
        )
    )
    o: Any = attr.ib(
        validator=attr.validators.not_(attr.validators.in_("abc"), msg="spam")
    )
    p: Any = attr.ib(
        validator=attr.validators.not_(attr.validators.in_("abc"), msg=None)
    )
    q: Any = attr.ib(
        validator=attrs.validators.optional(attrs.validators.instance_of(C))
    )
    r: Any = attr.ib(
        validator=attrs.validators.optional([attrs.validators.instance_of(C)])
    )
    s: Any = attr.ib(
        validator=attrs.validators.optional((attrs.validators.instance_of(C),))
    )


@attr.define
class Validated2:
    num: int = attr.field(validator=attr.validators.ge(0))


with attr.validators.disabled():
    Validated2(num=-1)


try:
    attr.validators.set_disabled(True)
    Validated2(num=-1)
finally:
    attr.validators.set_disabled(False)


# Custom repr()
@attr.s
class WithCustomRepr:
    a: int = attr.ib(repr=True)
    b: str = attr.ib(repr=False)
    c: str = attr.ib(repr=lambda value: "c is for cookie")
    d: bool = attr.ib(repr=str)


# Check some of our own types
@attr.s(eq=True, order=False)
class OrderFlags:
    a: int = attr.ib(eq=False, order=False)
    b: int = attr.ib(eq=True, order=True)


# on_setattr hooks
@attr.s(on_setattr=attr.setters.validate)
class ValidatedSetter:
    a: int
    b: str = attr.ib(on_setattr=attr.setters.NO_OP)
    c: bool = attr.ib(on_setattr=attr.setters.frozen)
    d: int = attr.ib(on_setattr=[attr.setters.convert, attr.setters.validate])
    e: bool = attr.ib(
        on_setattr=attr.setters.pipe(
            attr.setters.convert, attr.setters.validate
        )
    )


# field_transformer
def ft_hook(cls: type, attribs: list[attr.Attribute]) -> list[attr.Attribute]:
    return attribs


@attr.s(field_transformer=ft_hook)
class TransformedAttrs:
    x: int


# Auto-detect
@attr.s(auto_detect=True)
class AutoDetect:
    x: int

    def __init__(self, x: int):
        self.x = x


@attr.define(order=True)
class NGClass:
    x: int = attr.field(default=42)


ngc = NGClass(1)


@attr.frozen(str=True)
class NGFrozen:
    x: int


attr.fields(NGFrozen).x.evolve(eq=False)
a = attrs.fields(NGFrozen).x
a.evolve(repr=False)


@attr.s(collect_by_mro=True)
class MRO:
    pass


@attr.s
class FactoryTest:
    a: list[int] = attr.ib(default=attr.Factory(list))
    b: list[Any] = attr.ib(default=attr.Factory(list, False))
    c: list[int] = attr.ib(default=attr.Factory((lambda s: s.a), True))


attr.asdict(FactoryTest(), tuple_keys=True)


# Check match_args stub
@attr.s(match_args=False)
class MatchArgs:
    a: int = attr.ib()
    b: int = attr.ib()


attr.asdict(FactoryTest())
attr.asdict(FactoryTest(), retain_collection_types=False)


def accessing_from_attr() -> None:
    """
    Use a function to keep the ns clean.
    """
    attr.converters.optional
    attr.exceptions.FrozenError
    attr.filters.include
    attr.filters.exclude
    attr.setters.frozen
    attr.validators.and_
    attr.cmp_using


foo = object
if attrs.has(foo) or attr.has(foo):
    foo.__attrs_attrs__


@attrs.define(unsafe_hash=True)
class Hashable:
    pass


def test(cls: type) -> None:
    if attr.has(cls):
        attr.resolve_types(cls)
